"""
cURL命令验证器
负责验证curl命令的安全性和有效性
"""
import re
import logging
from urllib.parse import urlparse

logger = logging.getLogger(__name__)


class CurlValidator:
    """cURL命令验证器"""
    
    # 危险参数列表 - 只包含真正危险的文件操作和系统级参数
    DANGEROUS_PARAMS = [
        '--output', '-o',           # 文件输出
        '--upload-file', '-T',      # 文件上传
        '--write-out', '-w',        # 写入文件
        '--config', '-K',           # 配置文件
        '--netrc-file',             # netrc文件
        '--cookie-jar', '-c',       # cookie文件
        '--dump-header', '-D',      # 头部转储到文件
        '--trace', '--trace-ascii', # 跟踪输出到文件
        '--stderr',                 # 错误输出重定向
        '--unix-socket',            # Unix socket
        '--abstract-unix-socket',   # 抽象Unix socket
    ]
    
    # 允许的参数 - 这些是curl的标准参数，应该被允许
    ALLOWED_PARAMS = [
        '--data', '-d',             # POST数据
        '--header', '-H',           # HTTP头
        '--request', '-X',          # HTTP方法
        '--user-agent', '-A',       # User-Agent
        '--cookie', '-b',           # Cookie（不是cookie-jar）
        '--form', '-F',             # 表单数据（移除文件上传限制，因为可以用于JSON）
        '--data-binary',            # 二进制数据（移除限制，因为常用于API）
        '--compressed',             # 压缩
        '--location', '-L',         # 跟随重定向
        '--silent', '-s',           # 静默模式
        '--show-error', '-S',       # 显示错误
        '--fail', '-f',             # 失败时退出
        '--include', '-i',          # 包含响应头
        '--verbose', '-v',          # 详细输出
    ]
    
    # 允许的协议
    ALLOWED_PROTOCOLS = ['http', 'https']
    
    # 最大命令长度
    MAX_COMMAND_LENGTH = 8192
    
    def __init__(self):
        self.logger = logger
    
    def validate_command(self, curl_command: str) -> dict:
        """
        验证curl命令
        
        Args:
            curl_command: curl命令字符串
            
        Returns:
            dict: 验证结果 {'valid': bool, 'error': str, 'warnings': list}
        """
        result = {
            'valid': True,
            'error': None,
            'warnings': []
        }
        
        try:
            # 基本格式检查
            basic_check = self._check_basic_format(curl_command)
            if not basic_check['valid']:
                result.update(basic_check)
                return result
            
            # 长度检查
            length_check = self._check_length(curl_command)
            if not length_check['valid']:
                result.update(length_check)
                return result
            
            # 危险参数检查
            danger_check = self._check_dangerous_params(curl_command)
            if not danger_check['valid']:
                result.update(danger_check)
                return result
            
            # URL协议检查
            url_check = self._check_url_protocol(curl_command)
            if not url_check['valid']:
                result.update(url_check)
                return result
            
            # 命令注入检查
            injection_check = self._check_command_injection(curl_command)
            if not injection_check['valid']:
                result.update(injection_check)
                return result
            
            # 收集警告信息
            warnings = []
            warnings.extend(basic_check.get('warnings', []))
            warnings.extend(length_check.get('warnings', []))
            warnings.extend(danger_check.get('warnings', []))
            warnings.extend(url_check.get('warnings', []))
            warnings.extend(injection_check.get('warnings', []))
            
            result['warnings'] = warnings
            
            self.logger.info(f"curl命令验证通过: {curl_command[:100]}...")
            
        except Exception as e:
            self.logger.error(f"curl命令验证失败: {e}")
            result['valid'] = False
            result['error'] = f"验证过程中发生错误: {str(e)}"
        
        return result
    
    def _check_basic_format(self, curl_command: str) -> dict:
        """检查基本格式"""
        result = {'valid': True, 'warnings': []}
        
        # 去除首尾空白
        curl_command = curl_command.strip()
        
        # 检查是否以curl开头
        if not curl_command.lower().startswith('curl'):
            result['valid'] = False
            result['error'] = "命令必须以'curl'开头"
            return result
        
        # 检查是否包含URL
        if not re.search(r'https?://\S+', curl_command):
            result['valid'] = False
            result['error'] = "未找到有效的HTTP/HTTPS URL"
            return result
        
        return result
    
    def _check_length(self, curl_command: str) -> dict:
        """检查命令长度"""
        result = {'valid': True, 'warnings': []}
        
        if len(curl_command) > self.MAX_COMMAND_LENGTH:
            result['valid'] = False
            result['error'] = f"命令长度超过限制 ({self.MAX_COMMAND_LENGTH} 字符)"
            return result
        
        if len(curl_command) > 2048:
            result['warnings'].append("命令较长，请确认是否正确")
        
        return result
    
    def _check_dangerous_params(self, curl_command: str) -> dict:
        """检查危险参数"""
        result = {'valid': True, 'warnings': []}
        
        # 使用更精确的方法：分割命令为参数数组，然后检查每个参数
        # 这样可以避免子字符串匹配的问题
        import shlex
        try:
            # 使用shlex安全地分割命令行参数
            args = shlex.split(curl_command)
        except ValueError:
            # 如果shlex分割失败，使用简单分割
            args = curl_command.split()
        
        for i, arg in enumerate(args):
            # 检查当前参数是否在危险参数列表中
            if arg in self.DANGEROUS_PARAMS:
                result['valid'] = False
                result['error'] = f"包含危险参数: {arg}"
                return result
            
            # 检查长参数形式（如 --output=file）
            if '=' in arg:
                param_name = arg.split('=')[0]
                if param_name in self.DANGEROUS_PARAMS:
                    result['valid'] = False
                    result['error'] = f"包含危险参数: {param_name}"
                    return result
        
        # 检查文件路径相关的模式
        file_patterns = [
            r'>\s*\S+',  # 输出重定向
            r'<\s*\S+',  # 输入重定向
        ]
        
        for pattern in file_patterns:
            if re.search(pattern, curl_command, re.IGNORECASE):
                result['valid'] = False
                result['error'] = "包含文件操作，存在安全风险"
                return result
        
        return result
    
    def _check_url_protocol(self, curl_command: str) -> dict:
        """检查URL协议"""
        result = {'valid': True, 'warnings': []}
        
        # 提取所有URL
        urls = re.findall(r'https?://\S+', curl_command)
        
        for url in urls:
            try:
                # 清理URL（移除引号等）
                clean_url = url.strip('\'"')
                parsed = urlparse(clean_url)
                
                if parsed.scheme not in self.ALLOWED_PROTOCOLS:
                    result['valid'] = False
                    result['error'] = f"不支持的协议: {parsed.scheme}"
                    return result
                
                # 检查是否为内网地址
                if self._is_internal_ip(parsed.hostname):
                    result['warnings'].append(f"检测到内网地址: {parsed.hostname}")
                
            except Exception as e:
                result['warnings'].append(f"URL解析警告: {url} - {str(e)}")
        
        return result
    
    def _check_command_injection(self, curl_command: str) -> dict:
        """检查命令注入"""
        result = {'valid': True, 'warnings': []}
        
        # 危险字符和模式 - 更精确的检测，避免误报
        dangerous_patterns = [
            r';\s*\w+',     # 分号后跟命令（不是在引号内的分号）
            r'\|\s*\w+',    # 管道后跟命令
            r'&&\s*\w+',    # 逻辑与后跟命令
            r'\|\|\s*\w+',  # 逻辑或后跟命令
            r'`[^`]*`',     # 反引号执行
            r'\$\([^)]*\)', # 命令替换
            r'>\s*/',       # 根目录写入
            r'\brm\s+',     # 删除命令
            r'\bsudo\s+',   # 提权命令
            r'\bchmod\s+',  # 权限修改
            r'\bwget\s+',   # 其他下载命令
            r'\bcat\s+',    # 文件读取
            r'\becho\s+.*>',# echo重定向写入
        ]
        
        # 先移除引号内的内容，避免误报header中的分号等
        temp_command = self._remove_quoted_content(curl_command)
        
        for pattern in dangerous_patterns:
            if re.search(pattern, temp_command):
                result['valid'] = False
                result['error'] = f"检测到潜在的命令注入: {re.search(pattern, temp_command).group()}"
                return result
        
        return result
    
    def _remove_quoted_content(self, command: str) -> str:
        """移除引号内的内容，避免误报"""
        # 移除双引号内的内容
        temp = re.sub(r'"[^"]*"', '""', command)
        # 移除单引号内的内容
        temp = re.sub(r"'[^']*'", "''", temp)
        return temp
    
    def _is_internal_ip(self, hostname: str) -> bool:
        """检查是否为内网IP"""
        if not hostname:
            return False
        
        # 内网IP模式
        internal_patterns = [
            r'^127\.',          # 127.x.x.x
            r'^10\.',           # 10.x.x.x
            r'^192\.168\.',     # 192.168.x.x
            r'^172\.(1[6-9]|2[0-9]|3[0-1])\.',  # 172.16-31.x.x
            r'^localhost$',     # localhost
            r'^0\.0\.0\.0$',    # 0.0.0.0
        ]
        
        for pattern in internal_patterns:
            if re.match(pattern, hostname, re.IGNORECASE):
                return True
        
        return False
    
    def sanitize_command(self, curl_command: str) -> str:
        """
        清理curl命令，移除潜在的危险部分
        
        Args:
            curl_command: 原始curl命令
            
        Returns:
            str: 清理后的curl命令
        """
        # 移除首尾空白
        command = curl_command.strip()
        
        # 移除多余的空格
        command = re.sub(r'\s+', ' ', command)
        
        # 确保以curl开头
        if not command.lower().startswith('curl'):
            command = 'curl ' + command
        
        return command